/*
 *  Class name: PageableListPanel.java
 *  Version: 1.0
 *  Date: 7.12.2013
 */
package cz.muni.fi.pa165.soccergamesarchive.restclient.gui.components.pageablelist;

import cz.muni.fi.pa165.soccergamesarchive.restclient.gui.components.enums.Operation;
import cz.muni.fi.pa165.soccergamesarchive.restclient.gui.components.interfaces.ResultListenable;
import cz.muni.fi.pa165.soccergamesarchive.restclient.gui.components.pageablelist.items.PageableListItemButton;
import cz.muni.fi.pa165.soccergamesarchive.restclient.rest.EntityRestClient;
import cz.muni.fi.pa165.soccergamesarchive.restclient.utils.JOptionDialogWindowHandler;
import java.awt.Cursor;
import java.awt.Window;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

/**
 *
 * @author Michal Ďuriš
 * @param <T>
 */
public abstract class PageableListPanel<T> extends JPanel implements
        ResultListenable {

    private static final long serialVersionUID = 1L;

    private enum PageableListPanelAction {

        NO_ACTION,
        NEXT_PAGE,
        PREVIOUS_PAGE
    };

    private Window owner = null;
    private String panelTitle = "Default title";

    private PageableListPanelAction listActionHistory = PageableListPanelAction.NO_ACTION;

    private List<T> parentTargetEntityList;
    private List<PageableListItemButton<T>> parentTargetItemList;

    private PageableListItemButton<T> selectedItem;

    private int firstItemIndex = 0;
    private int maxItemsOnPage = 20;

    private int currentPage = 1;
    private int totalPages = 1;

    public PageableListPanel() {
        super();

        initComponents();
    }

    /**
     * Creates new form PageableListPanel
     *
     * @param owner
     * @param panelTitle
     */
    public PageableListPanel(Window owner, String panelTitle) {
        super();

        this.owner = owner;
        this.panelTitle = panelTitle;

        initComponents();
    }

    public abstract void init();

    public abstract void init(List<T> entityList);

    public abstract PageableListItemButton<T> createEntityListItem();

    public abstract PageableListItemButton<T> createEntityListItem(T entity);

    protected abstract EntityRestClient<T> getRestClientImplementation();

    protected abstract void handleAddItemAction();

    private class PageableEntityListPanelSwingWorker extends SwingWorker<List<T>, Void> {

        private final EntityRestClient<T> restClient;

        private Operation operation = Operation.NO_OP;

        private PageableListItemButton<T> currentEntityItem;
        private PageableListItemButton<T> newEntityItem;

        private ResultListenable requestSourceComponent;

        public PageableEntityListPanelSwingWorker() {
            restClient = getRestClientImplementation();
        }

        protected PageableEntityListPanelSwingWorker processGetRequest(
                ResultListenable requestSourceComponent) {

            operation = Operation.GET;

            this.requestSourceComponent = requestSourceComponent;

            return this;
        }

        protected PageableEntityListPanelSwingWorker processAddRequest(
                PageableListItemButton<T> newEntityItem,
                ResultListenable requestSourceComponent) {

            if (newEntityItem == null) {
                throw new IllegalArgumentException();
            }

            operation = Operation.ADD;

            this.newEntityItem = newEntityItem;
            this.requestSourceComponent = requestSourceComponent;

            return this;
        }

        protected PageableEntityListPanelSwingWorker processChangeRequest(
                PageableListItemButton<T> currentEntityItem,
                PageableListItemButton<T> newEntityItem,
                ResultListenable requestSourceComponent) {

            if (currentEntityItem == null && newEntityItem == null) {
                throw new IllegalArgumentException();
            }

            operation = Operation.CHANGE;

            this.currentEntityItem = currentEntityItem;
            this.newEntityItem = newEntityItem;
            this.requestSourceComponent = requestSourceComponent;

            return this;
        }

        protected PageableEntityListPanelSwingWorker processRemoveRequest(
                PageableListItemButton<T> entityItem,
                ResultListenable requestSourceComponent) {

            if (entityItem == null) {
                throw new IllegalArgumentException();
            }

            operation = Operation.REMOVE;

            currentEntityItem = entityItem;

            return this;
        }

        @Override
        protected List<T> doInBackground() throws Exception {

            if (restClient.connect()) {

                owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

                List<T> resultList = new ArrayList<>(1);

                switch (operation) {
                    case GET:
                        resultList = restClient.getEntityList(firstItemIndex,
                                maxItemsOnPage);

                        int totalCount = restClient.getAllEntitiesCount();

                        if (totalCount > 0) {

                            totalPages = calculateTotalPagesCount(totalCount);
                            enableNavigationButtons();
                        }

                        break;
                    case ADD:
                        resultList.add(restClient.addEntity(
                                newEntityItem.getEntity()));
                        break;
                    case CHANGE:
                        resultList.add(restClient.changeEntity(
                                newEntityItem.getEntity()));
                        break;
                    case REMOVE:
                        restClient.removeEntity(currentEntityItem.getEntity());
                        break;
                    default:
                        //@TODO: CHANGE IT.... MAYBE
                        throw new RuntimeException();
                }

                return resultList;
            } else {

                JOptionDialogWindowHandler.showErrorDialogWindow(owner,
                        "Error",
                        "Unable to connect to the server. Try again later.");
                throw new RuntimeException();
            }
        }

        @Override
        protected void done() {

            try {
                owner.setCursor(Cursor
                        .getPredefinedCursor(Cursor.DEFAULT_CURSOR));

                List<T> entityList = get();

                switch (operation) {
                    case GET:
                        processItemList(processEntityList(entityList, false));
                        break;

                    case ADD:
                        newEntityItem.setEntity(entityList.get(0));

                        if (parentTargetItemList != null
                                && parentTargetEntityList != null) {

                            parentTargetItemList.add(newEntityItem);
                            parentTargetEntityList.add(
                                    newEntityItem.getEntity());
                        }

                        getItemList();
                        break;

                    case CHANGE:
                        currentEntityItem.setEntity(entityList.get(0));

                        getItemList();
                        break;

                    case REMOVE:
                        if (parentTargetItemList != null
                                && parentTargetEntityList != null) {

                            parentTargetItemList.remove(currentEntityItem);
                            parentTargetEntityList.remove(
                                    currentEntityItem.getEntity());
                        }

                        getItemList();
                        break;

                    default:
                        //@TODO: CHANGE IT.... MAYBE
                        throw new RuntimeException();
                }

                if (requestSourceComponent != null) {
                    requestSourceComponent.processSuccessResult();
                }

            } catch (ExecutionException | InterruptedException |
                     RuntimeException ex) {

                System.err.println(getName() + " " + ex);

                if (requestSourceComponent != null) {
                    requestSourceComponent.processFailResult();
                } else {
                    JOptionDialogWindowHandler.showErrorDialogWindow(owner,
                            "Error", "Operation execution failed !!! :( ");
                }
            } finally {
                restClient.disconnect();
            }
        }

    }

    public void setSelectedItem(PageableListItemButton<T> selectedItem) {

        if (selectedItem != null && this.selectedItem != null
                && this.selectedItem != selectedItem) {
            this.selectedItem.unselectItem();
        }

        this.selectedItem = selectedItem;
    }

    public void setMaxItemsOnPage(int maxItemsOnPage) {
        this.maxItemsOnPage = maxItemsOnPage;
    }

    public void setPanelTitle(String panelTitle) {
        this.panelTitle = panelTitle;
    }

    public void setOwner(Window owner) {
        this.owner = owner;
    }

    public Window getOwner() {
        return owner;
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        actionPanel = new javax.swing.JPanel();
        addButton = new javax.swing.JButton();
        listScrollPanel = new javax.swing.JScrollPane();
        listPanel = new javax.swing.JPanel();
        navigationPanel = new javax.swing.JPanel();
        previousPageButton = new javax.swing.JButton();
        nextPageButton = new javax.swing.JButton();
        navigationStatusLabel = new javax.swing.JLabel();

        setBorder(javax.swing.BorderFactory.createTitledBorder(null, panelTitle, javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Arial", 1, 14), new java.awt.Color(0, 102, 255))); // NOI18N
        setLayout(new java.awt.BorderLayout(0, 2));

        actionPanel.setPreferredSize(new java.awt.Dimension(140, 16));
        actionPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT, 0, 0));

        addButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/green-add-icon.png"))); // NOI18N
        addButton.setBorder(null);
        addButton.setContentAreaFilled(false);
        addButton.setFocusPainted(false);
        addButton.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        addButton.setMaximumSize(new java.awt.Dimension(20, 20));
        addButton.setMinimumSize(new java.awt.Dimension(20, 20));
        addButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/blue-add-icon.png"))); // NOI18N
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });
        actionPanel.add(addButton);

        add(actionPanel, java.awt.BorderLayout.PAGE_START);

        listScrollPanel.setPreferredSize(new java.awt.Dimension(130, 200));

        listPanel.setLayout(new javax.swing.BoxLayout(listPanel, javax.swing.BoxLayout.PAGE_AXIS));
        listScrollPanel.setViewportView(listPanel);

        add(listScrollPanel, java.awt.BorderLayout.CENTER);

        navigationPanel.setPreferredSize(new java.awt.Dimension(120, 30));
        navigationPanel.setLayout(new java.awt.BorderLayout(10, 0));

        previousPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/arrow-left-icon.png"))); // NOI18N
        previousPageButton.setBorder(null);
        previousPageButton.setContentAreaFilled(false);
        previousPageButton.setEnabled(false);
        previousPageButton.setFocusPainted(false);
        previousPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/arrow-skip-left-icon.png"))); // NOI18N
        previousPageButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                previousPageButtonActionPerformed(evt);
            }
        });
        navigationPanel.add(previousPageButton, java.awt.BorderLayout.LINE_START);

        nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/arrow-right-icon.png"))); // NOI18N
        nextPageButton.setBorder(null);
        nextPageButton.setContentAreaFilled(false);
        nextPageButton.setEnabled(false);
        nextPageButton.setFocusPainted(false);
        nextPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/components/arrow-skip-right-icon.png"))); // NOI18N
        nextPageButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                nextPageButtonActionPerformed(evt);
            }
        });
        navigationPanel.add(nextPageButton, java.awt.BorderLayout.LINE_END);

        navigationStatusLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        navigationPanel.add(navigationStatusLabel, java.awt.BorderLayout.CENTER);

        add(navigationPanel, java.awt.BorderLayout.PAGE_END);
    }// </editor-fold>//GEN-END:initComponents

    private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
        handleNextPageAction();
    }//GEN-LAST:event_nextPageButtonActionPerformed

    private void previousPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_previousPageButtonActionPerformed
        handlePreviousPageAction();
    }//GEN-LAST:event_previousPageButtonActionPerformed

    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
        handleAddItemAction();
    }//GEN-LAST:event_addButtonActionPerformed

    private void handleNextPageAction() {
        if (totalPages > 1 && (currentPage + 1) <= totalPages) {

            listActionHistory = PageableListPanelAction.NEXT_PAGE;

            goToTheNextPage();
            getItemList();
        }
    }

    private void handlePreviousPageAction() {
        if ((currentPage - 1) > 0) {

            listActionHistory = PageableListPanelAction.PREVIOUS_PAGE;

            goToThePreviousPage();
            getItemList();
        }
    }

    private void goToTheNextPage() {
        currentPage++;
        firstItemIndex += maxItemsOnPage;
    }

    private void goToThePreviousPage() {
        currentPage--;
        firstItemIndex -= maxItemsOnPage;
    }

    private void enableNavigationButtons() {
        previousPageButton.setEnabled((currentPage != 1));
        nextPageButton.setEnabled((currentPage < totalPages));
    }

    private int calculateTotalPagesCount(int itemsCount) {
        return itemsCount > maxItemsOnPage ? ((itemsCount / maxItemsOnPage) + 1)
                : 1;
    }

    @Override
    public void processSuccessResult() {

        enableNavigationButtons();

        navigationStatusLabel.setText(
                new StringBuilder().append("page ").append(currentPage).append(
                        " from ").append(totalPages).append(
                        totalPages == 1 ? " page" : " pages").toString());

        listActionHistory = PageableListPanelAction.NO_ACTION;
    }

    @Override
    public void processFailResult() {

        JOptionDialogWindowHandler.showErrorDialogWindow(owner, "Error",
                "List loading failed! Restoring previous state.");

        if (listActionHistory == PageableListPanelAction.NEXT_PAGE) {

            goToThePreviousPage();
        } else if (listActionHistory == PageableListPanelAction.PREVIOUS_PAGE) {

            goToTheNextPage();
        }

        listActionHistory = PageableListPanelAction.NO_ACTION;
    }

    protected void initializeList(List<T> entityList) {
        if (entityList == null) {
            getItemList();
        } else {

            parentTargetEntityList = entityList;
            parentTargetItemList = processEntityList(entityList, true);
            limitAndProcessItemList(parentTargetItemList);
        }
    }

    protected List<PageableListItemButton<T>> processEntityList(
            List<T> entityList, boolean calculateTotalPagesCount) {

        List<PageableListItemButton<T>> createdItemList = new ArrayList<>();

        if (calculateTotalPagesCount && entityList != null && !entityList
                .isEmpty()) {

            totalPages = calculateTotalPagesCount(entityList.size());
            enableNavigationButtons();
        }

        for (T entity : entityList) {

            PageableListItemButton<T> createdEntityItem = createEntityListItem(
                    entity);

            if (createdEntityItem != null) {
                createdItemList.add(createdEntityItem);
            }
        }

        return createdItemList;
    }

    protected void processItemList(List<PageableListItemButton<T>> itemList) {

        if (itemList == null) {
            return;
        }

        listPanel.removeAll();

        itemList.stream().forEach(listPanel::add);

        updateListComponent();
    }

    protected void limitAndProcessItemList(
            List<PageableListItemButton<T>> itemList) {

        if (itemList != null) {

            processItemList(itemList.subList(firstItemIndex,
                    currentPage * maxItemsOnPage > itemList.size() ? itemList
                    .size() : currentPage * maxItemsOnPage));

            processSuccessResult();
        }
    }

    protected void getItemList() {

        if (parentTargetItemList != null) {

            limitAndProcessItemList(parentTargetItemList);

        } else {

            new PageableEntityListPanelSwingWorker().processGetRequest(this)
                    .execute();
        }
    }

    public void updateList() {
        getItemList();
    }

    public void addListItem(PageableListItemButton<T> listItem,
                            ResultListenable requestSourceComponent) {

        if (listItem != null) {

            new PageableEntityListPanelSwingWorker().processAddRequest(listItem,
                    requestSourceComponent).execute();
        }
    }

    public void changeListItem(PageableListItemButton<T> currentListItem,
                               PageableListItemButton<T> newListItem,
                               ResultListenable requestSourceComponent) {

        if (currentListItem != null && newListItem != null) {

            new PageableEntityListPanelSwingWorker().processChangeRequest(
                    currentListItem, newListItem, requestSourceComponent)
                    .execute();
        }
    }

    public void removeListItem(PageableListItemButton<T> listItem,
                               ResultListenable requestSourceComponent) {

        if (listItem != null) {

            new PageableEntityListPanelSwingWorker().processRemoveRequest(
                    listItem,
                    requestSourceComponent).execute();
        }
    }

    public boolean removeListItemFromParentList(
            PageableListItemButton<T> listItem) {

        boolean result = parentTargetItemList.remove(listItem)
                && parentTargetEntityList.remove(listItem.getEntity());

        if (result) {
            getItemList();
        }

        return result;
    }

    public PageableListItemButton<T> getSelectedItem() {
        return selectedItem;
    }

    private void updateListComponent() {
        validate();
        owner.repaint();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    protected javax.swing.JPanel actionPanel;
    private javax.swing.JButton addButton;
    private javax.swing.JPanel listPanel;
    private javax.swing.JScrollPane listScrollPanel;
    private javax.swing.JPanel navigationPanel;
    private javax.swing.JLabel navigationStatusLabel;
    private javax.swing.JButton nextPageButton;
    private javax.swing.JButton previousPageButton;
    // End of variables declaration//GEN-END:variables

}
